﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Reflection;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;

namespace Slex {
    /// <summary>
    /// 用于动态绑定，继承者可以动态绑定
    /// from microsoft community,original name is CustomTypeHelper<T>
    /// don't need define inherit class 
    /// ExtensibleType<Employee>.AddProperty("NewPro");
    /// var x=ExtensibleType<Employee>();
    /// x.SetPropertyValue("Name","Tom");//this is a Employee.Name
    /// x.SetPropertyValue("NewPro","111");//this is new add property 'NewPro'
    /// that's all.
    /// </summary>
    /// <typeparam name="T"></typeparam>    
    public class ExtensibleType<T> : ICustomTypeProvider, INotifyPropertyChanged {
        static List<CustomPropertyInfoHelper> _customProperties = new List<CustomPropertyInfoHelper>();
        Dictionary<string, object> _customPropertyValues;
        static Dictionary<string, PropertyInfo> pis;
        private CustomType _ctype;
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info) {
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        public ExtensibleType() {
            if (pis == null)
                pis = new Dictionary<string, PropertyInfo>();
            var entityProps = new List<PropertyInfo>();
            var h1 = typeof(T).GetProperties();
            foreach (var item in h1) {
                var mark = true;
                foreach (var item2 in _customProperties) {
                    if (item.Name == item2.Name) {
                        mark = false; break;
                    }
                }
                if (mark) {
                    entityProps.Add(item);
                }
            }
            foreach (var item in entityProps) {
                //if (!(from p in _customProperties select p.Name).Contains(item.Name))
                _customProperties.Add(new CustomPropertyInfoHelper(item.Name, item.DeclaringType));
                pis.Add(item.Name, item);
            }
            _customPropertyValues = new Dictionary<string, object>();
            foreach (var property in this.GetCustomType().GetProperties()) {
                _customPropertyValues.Add(property.Name, null);
            }
            Entity = (T)Activator.CreateInstance(typeof(T));
        }
        public ExtensibleType(T entity)
            : this() {
            Entity = entity;
        }
        public new Type GetType() {
            return _ctype;
        }
        public static void AddProperty(string name) {
            if (!CheckIfNameExists(name)) {
                _customProperties.Add(new CustomPropertyInfoHelper(name, typeof(String)));
            }
        }
        public static void AddProperty(string name, Type propertyType) {
            if (!CheckIfNameExists(name)) {
                _customProperties.Add(new CustomPropertyInfoHelper(name, propertyType));
            }
        }
        public static void AddProperty(string name, Type propertyType, List<Attribute> attributes) {
            if (!CheckIfNameExists(name)) {
                _customProperties.Add(new CustomPropertyInfoHelper(name, propertyType, attributes));
            }
        }
        private static bool CheckIfNameExists(string name) {
            if ((from p in _customProperties select p.Name).Contains(name))
                //throw new Exception("The property with this name already exists: " + name);
                return true;
            else return false;
        }
        public T Entity { get; private set; }
        public void SetPropertyValue(string propertyName, object value) {
            CustomPropertyInfoHelper propertyInfo = (from prop in _customProperties where prop.Name == propertyName select prop).FirstOrDefault();
            if (!_customPropertyValues.ContainsKey(propertyName))
                throw new Exception("There is no property " + propertyName);
            if (propertyInfo != null && ValidateValueType(value, propertyInfo._type)) {
                if (_customPropertyValues[propertyName] != value) {
                    _customPropertyValues[propertyName] = value;
                    NotifyPropertyChanged(propertyName);
                }
            } else {
                pis[propertyName].SetValue(Entity, value, null);
                if (_customPropertyValues[propertyName] != value) {
                    _customPropertyValues[propertyName] = value;
                    NotifyPropertyChanged(propertyName);
                }
            }
            //else throw new Exception("Value is of the wrong type or null for a non-nullable type.");
        }
        private bool ValidateValueType(object value, Type type) {
            if (value == null)
                // Non-value types can be assigned null.
                if (!type.IsValueType)
                    return true;
                else
                    // Check if the type if a Nullable type.
                    return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
            else
                return type.IsAssignableFrom(value.GetType());
        }
        public dynamic GetPropertyValue(string propertyName) {
            if (_customPropertyValues.ContainsKey(propertyName)) {
                var x1 = _customPropertyValues[propertyName];
                if (pis.ContainsKey(propertyName)) {
                    var pi = pis[propertyName];
                    if (pi != null) {
                        var x2 = pi.GetValue(Entity, null);
                        if (x1.Equals(x2)) {
                            return x1;
                        } else {
                            _customPropertyValues[propertyName] = x2;
                            return x2;
                        }
                    }
                }
                return x1;
            } else {
                throw new Exception("There is no property " + propertyName);
            }
        }
        public PropertyInfo[] GetProperties() {
            return this.GetCustomType().GetProperties();
        }
        public Type GetCustomType() {
            if (_ctype == null) {
                _ctype = new CustomType(typeof(T));
            }
            return _ctype;
        }
        private class CustomType : Type {
            Type _baseType;
            public CustomType(Type delegatingType) {
                _baseType = delegatingType;
            }
            public override Assembly Assembly {
                get { return _baseType.Assembly; }
            }
            public override string AssemblyQualifiedName {
                get { return _baseType.AssemblyQualifiedName; }
            }
            public override Type BaseType {
                get { return _baseType.BaseType; }
            }
            public override string FullName {
                get { return _baseType.FullName; }
            }
            public override Guid GUID {
                get { return _baseType.GUID; }
            }
            protected override TypeAttributes GetAttributeFlagsImpl() {
                throw new NotImplementedException();
            }
            protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) {
                throw new NotImplementedException();
            }
            public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) {
                return _baseType.GetConstructors(bindingAttr);
            }
            public override Type GetElementType() {
                return _baseType.GetElementType();
            }
            public override EventInfo GetEvent(string name, BindingFlags bindingAttr) {
                return _baseType.GetEvent(name, bindingAttr);
            }
            public override EventInfo[] GetEvents(BindingFlags bindingAttr) {
                return _baseType.GetEvents(bindingAttr);
            }
            public override FieldInfo GetField(string name, BindingFlags bindingAttr) {
                return _baseType.GetField(name, bindingAttr);
            }
            public override FieldInfo[] GetFields(BindingFlags bindingAttr) {
                return _baseType.GetFields(bindingAttr);
            }
            public override Type GetInterface(string name, bool ignoreCase) {
                return _baseType.GetInterface(name, ignoreCase);
            }
            public override Type[] GetInterfaces() {
                return _baseType.GetInterfaces();
            }
            public override MemberInfo[] GetMembers(BindingFlags bindingAttr) {
                return _baseType.GetMembers(bindingAttr);
            }
            protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) {
                throw new NotImplementedException();
            }
            public override MethodInfo[] GetMethods(BindingFlags bindingAttr) {
                return _baseType.GetMethods(bindingAttr);
            }
            public override Type GetNestedType(string name, BindingFlags bindingAttr) {
                return _baseType.GetNestedType(name, bindingAttr);
            }
            public override Type[] GetNestedTypes(BindingFlags bindingAttr) {
                return _baseType.GetNestedTypes(bindingAttr);
            }
            public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) {
                //PropertyInfo[] clrProperties = _baseType.GetProperties(bindingAttr);
                //if (clrProperties != null)
                //    return clrProperties.Concat(_customProperties).ToArray();
                //else
                return _customProperties.ToArray();
            }
            protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) {
                // Look for the CLR property with this name first.
                PropertyInfo propertyInfo = (from prop in GetProperties(bindingAttr) where prop.Name == name select prop).FirstOrDefault();
                if (propertyInfo == null) {
                    // If the CLR property was not found, return a custom property
                    propertyInfo = (from prop in _customProperties where prop.Name == name select prop).FirstOrDefault();
                }
                return propertyInfo;
            }
            protected override bool HasElementTypeImpl() {
                throw new NotImplementedException();
            }
            public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] namedParameters) {
                return _baseType.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);
            }
            protected override bool IsArrayImpl() {
                throw new NotImplementedException();
            }
            protected override bool IsByRefImpl() {
                throw new NotImplementedException();
            }
            protected override bool IsCOMObjectImpl() {
                throw new NotImplementedException();
            }
            protected override bool IsPointerImpl() {
                throw new NotImplementedException();
            }
            protected override bool IsPrimitiveImpl() {
                return _baseType.IsPrimitive;
            }
            public override Module Module {
                get { return _baseType.Module; }
            }
            public override string Namespace {
                get { return _baseType.Namespace; }
            }
            public override Type UnderlyingSystemType {
                get { return _baseType.UnderlyingSystemType; }
            }
            public override object[] GetCustomAttributes(Type attributeType, bool inherit) {
                return _baseType.GetCustomAttributes(attributeType, inherit);
            }
            public override object[] GetCustomAttributes(bool inherit) {
                return _baseType.GetCustomAttributes(inherit);
            }
            public override bool IsDefined(Type attributeType, bool inherit) {
                return _baseType.IsDefined(attributeType, inherit);
            }
            public override string Name {
                get { return _baseType.Name; }
            }
        }
        // Custom implementation of the PropertyInfo
        private class CustomPropertyInfoHelper : PropertyInfo {
            public string _name;
            public Type _type;
            public List<Attribute> _attributes = new List<Attribute>();
            public CustomPropertyInfoHelper(string name, Type type) {
                _name = name;
                _type = type;
            }
            public CustomPropertyInfoHelper(string name, Type type, List<Attribute> attributes) {
                _name = name;
                _type = type;
                _attributes = attributes;
            }
            public override PropertyAttributes Attributes {
                get { throw new NotImplementedException(); }
            }
            public override bool CanRead {
                get { return true; }
            }
            public override bool CanWrite {
                get { return true; }
            }
            public override MethodInfo[] GetAccessors(bool nonPublic) {
                throw new NotImplementedException();
            }
            public override MethodInfo GetGetMethod(bool nonPublic) {
                throw new NotImplementedException();
            }
            public override ParameterInfo[] GetIndexParameters() {
                return new ParameterInfo[0];
            }
            public override MethodInfo GetSetMethod(bool nonPublic) {
                throw new NotImplementedException();
            }
            // Returns the value from the dictionary stored in the Customer's instance.
            public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture) {
                return obj.GetType().GetMethod("GetPropertyValue").Invoke(obj, new[] { _name });
            }
            public override Type PropertyType {
                get { return _type; }
            }
            // Sets the value in the dictionary stored in the Customer's instance.
            public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture) {
                obj.GetType().GetMethod("SetPropertyValue").Invoke(obj, new[] { _name, value });
            }
            public override Type DeclaringType {
                get { throw new NotImplementedException(); }
            }
            public override object[] GetCustomAttributes(Type attributeType, bool inherit) {
                var attrs = from a in _attributes where a.GetType() == attributeType select a;
                return attrs.ToArray();
            }
            public override object[] GetCustomAttributes(bool inherit) {
                return _attributes.ToArray();
            }
            public override bool IsDefined(Type attributeType, bool inherit) {
                throw new NotImplementedException();
            }
            public override string Name {
                get { return _name; }
            }
            public override Type ReflectedType {
                get { throw new NotImplementedException(); }
            }
            internal List<Attribute> CustomAttributesInternal {
                get { return _attributes; }
            }
        }
    }
}
